﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GradesParser.xaml.cs" company="TomCorp">
//   Thomas Heuberger
// </copyright>
// <summary>
//   Interaction logic for GradesParser.xaml
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using Microsoft.Exchange.WebServices.Data;

namespace FHIntranetParser {
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Xml.Linq;
    using System.Xml.Serialization;
    using HtmlAgilityPack;
    using mshtml;
    using Path = System.IO.Path;

    /// <summary>
    /// Interaction logic for GradesParser.xaml
    /// </summary>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
    public partial class GradesParser : UserControl {
        /// <summary>
        /// The timer property.
        /// </summary>
        public static readonly DependencyProperty TimerProperty = DependencyProperty.Register("Timer", typeof(string), typeof(GradesParser));

        /// <summary>
        /// A Second.
        /// </summary>
        private static readonly TimeSpan Second = new TimeSpan(0, 0, 0, 1);

        /// <summary>
        /// The config.
        /// </summary>
        private Config config;

        /// <summary>
        /// The firstpass.
        /// </summary>
        private bool firstpass = true;
        
        /// <summary>
        /// The grades uri.
        /// </summary>
        private Uri gradesUri;

        /// <summary>
        /// The timer.
        /// </summary>
        private FixedStepDispatcherTimer timer;

        /// <summary>
        /// Initializes a new instance of the <see cref="GradesParser"/> class.
        /// </summary>
        public GradesParser() {
            this.InitializeComponent();
        }

        /// <summary>
        /// Event handler for reporting log messages
        /// </summary>
        public event EventHandler<MessageEventArgs> Reporting = delegate { };

        /// <summary>
        /// Gets or sets the timer.
        /// </summary>
        public string Timer {
            get { return (string)GetValue(TimerProperty); }
            set { this.SetValue(TimerProperty, value); }
        }

        /// <summary>
        /// Gets the time span.
        /// </summary>
        public TimeSpan TimeSpan { get; private set; }
        
        /// <summary>
        /// Gets or sets the history file path.
        /// </summary>
        public string HistoryFilePath { get; set; }

        /// <summary>
        /// Gets or sets the config file path.
        /// </summary>
        public string ConfigFilePath { get; set; }
               
        /// <summary>
        /// The start parsing.
        /// </summary>
        /// <param name="interval">
        /// The interval.
        /// </param>
        public void StartParsing(TimeSpan interval) {
            if (this.timer != null && this.timer.IsRunning) return;

            this.TimeSpan = interval;

            this.timer = new FixedStepDispatcherTimer(Second);

            this.timer.Tick += (sender, args) => {
                this.TimeSpan = this.TimeSpan.Subtract(Second);
                this.Timer = this.TimeSpan.ToString("c");

                if (this.TimeSpan != TimeSpan.Zero) return;

                // start parsing and reset the interval
                this.StartParsing();
                this.TimeSpan = interval;
            };

            this.timer.Start();

            this.StartParsing();
        }

        /// <summary>
        /// Method for raising the Reporting event.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected virtual void OnReporting(MessageEventArgs e) {
            this.Reporting(this, e);
        } 

        /// <summary>
        /// The start parsing.
        /// </summary>
        private void StartParsing() {
            if (this.firstpass) {
                // this is the first pass. load the config from the XML
                try {
                    this.config = Config.LoadFromXml(this.ConfigFilePath);
                    this.FHWebBrowser.Navigate(this.config.URI);
                }
                catch (FileNotFoundException) {
                    MessageBox.Show("Config file not found. Aborting!");
                }
                catch (Exception exception) {
                    MessageBox.Show("Error parsing the config file. Aborting! Just for your reference, the error was: " + Environment.NewLine + exception.Message);
                }
            }
            else this.FHWebBrowser.Navigate(this.gradesUri);
        }
        
        /// <summary>
        /// The fh web browser_ load completed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
        private void FHWebBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) {
            var document = (IHTMLDocument3)FHWebBrowser.Document;          
            var innerHtml = document.documentElement.innerHTML;
            
            var htmlDocument = new HtmlDocument();
            htmlDocument.LoadHtml(innerHtml);

            // check if we were redirected to the login
            if (e.Uri.AbsolutePath.Contains("logon.aspx")) {
                // check if there was an error
                var error = htmlDocument.DocumentNode.SelectSingleNode("//*[@id='divError']");
                
                if (error != null) {
                    this.OnReporting(new MessageEventArgs { Message = "InvalidCredentials" });
                    this.timer.Stop();

                    return;
                }

                document.getElementById(this.config.UsernameInput).setAttribute("value", this.config.Username);
                document.getElementById(this.config.PasswordInput).setAttribute("value", this.config.Password);

                // get a reference to the login button
                var loginButton = (HTMLInputElement)document.getElementById("btnLogon");
                loginButton.click();
            } else {
                // check if the current page is the portal
                if (e.Uri.AbsolutePath == "/services/index.aspx") {                    
                    // ok. get all the <a> nodes that have an href attribute that contains noten.aspx
                    var node = htmlDocument.DocumentNode.SelectNodes("//a/@href[contains(., 'noten.aspx')]").Last();

                    // try and get the name
                    var studentInfoNode = htmlDocument.DocumentNode.SelectNodes("//a/@href[contains(., 'stu_main.aspx')]").Last();
                    var studentInfo = studentInfoNode.InnerText.Split('(');
                    var name = studentInfo[0];
                    var matnr = studentInfo[1].Remove(studentInfo[1].IndexOf(')'));

                    this.OnReporting(new MessageEventArgs { Message = "StudentInfo", Info = new Tuple<string, string, string>(name, matnr, string.Empty) });
                    
                    // hopefully the last node is the current one
                    this.gradesUri = new Uri(e.Uri, Path.GetDirectoryName(e.Uri.LocalPath) + "/" + node.Attributes["href"].Value);
                    FHWebBrowser.Navigate(this.gradesUri);
                } else {
                    // this is the grades page
                    var grades = new List<Grade>();
                    var rows = htmlDocument.DocumentNode.SelectNodes("//tr").Skip(1);
                    foreach (var row in rows) {
                        var grade = new Grade {
                            Grd = int.Parse(row.ChildNodes[1].InnerText.Replace("&nbsp;", string.Empty)),
                            Subject = row.ChildNodes[3].InnerText.Replace("&nbsp;", string.Empty),
                            Ects = int.Parse(row.ChildNodes[5].InnerText.Replace("&nbsp;", string.Empty)),
                            Type = row.ChildNodes[7].InnerText.Replace("&nbsp;", string.Empty),
                            Date = DateTime.Parse(row.ChildNodes[9].InnerText.Replace("&nbsp;", string.Empty)),
                            Semester = int.Parse(row.ChildNodes[11].InnerText.Replace("&nbsp;", string.Empty))
                        };
                        grades.Add(grade);
                    }

                    // display the latest update
                    GradesDataGrid.ItemsSource = grades;

                    try {
                        var xmlSerializer = new XmlSerializer(grades.GetType());

                        var historyXml = XDocument.Load(this.HistoryFilePath);
                        using (var reader = historyXml.Root.CreateReader()) {
                            var history = (List<Grade>)xmlSerializer.Deserialize(reader);

                            if (history.Count != grades.Count) {
                                var result = grades.Except(history);

                                var body = "New grades found: " + Environment.NewLine;
                                
                                this.OnReporting(new MessageEventArgs { Message = "NewGrades" });
                                foreach (var grade in result) {
                                    this.OnReporting(new MessageEventArgs { Message = "\t" + grade });
                                    body += grade + Environment.NewLine;
                                }
                                
                                this.DispatchEmail("New grade(s) found!", body, new List<string> { this.config.Username + "@fhwn.ac.at" }, new WebCredentials(this.config.Username, this.config.Password));
                            }
                        }
                    } catch (FileNotFoundException) {
                        // silently discard any FileNotFoundExceptions
                    } catch (Exception) {
                        // while we are at it, discard any other exceptions as well.
                    }

                    // serialize the object and add it to the history file
                    try {
                        var xDocument = new XDocument();
                        using (var writer = xDocument.CreateWriter()) {
                            // write xml into the writer
                            var xmlSerializer = new XmlSerializer(grades.GetType());
                            xmlSerializer.Serialize(writer, grades);
                        }

                        xDocument.Save(this.HistoryFilePath);
                    } catch (Exception) {
                        this.OnReporting(new MessageEventArgs { Message = "Error writing history file!" });
                    }

                    this.OnReporting(new MessageEventArgs {
                        Message = "FetchedGrades", 
                        Info = new Tuple<string, string, string>(grades.Count.ToString(), grades.Average(g => g.Grd).ToString("0.##"), grades.Sum(g => g.Ects).ToString()) });                         
                    this.firstpass = false;
                }
            }
        }

        /// <summary>
        /// Sends an email.
        /// </summary>
        /// <param name="subject">
        /// The subject.
        /// </param>
        /// <param name="body">
        /// The body.
        /// </param>
        /// <param name="recipients">
        /// The recipients.
        /// </param>
        /// <param name="credentials">
        /// The credentials.
        /// </param>
        private void DispatchEmail(string subject, string body, List<string> recipients, WebCredentials credentials) {
            // Create an email message and identify the Exchange service.
            var exchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP2) {
                Url = new Uri("http://mail.fhwn.ac.at/EWS/exchange.asmx"),
                Credentials = credentials
            };

            var message = new EmailMessage(exchangeService) { Subject = subject, Body = body };

            // Add properties to the email message.
            foreach (var recipient in recipients) {
                message.ToRecipients.Add(recipient);
            }

            // Send the email message and save a copy.
            message.SendAndSaveCopy();
        }
    }
}
